﻿#if NETSTANDARD2_0 || NETCOREAPP3_0
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
#else
using System.Web;
#endif
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;

namespace Longbow.Cache
{
    /// <summary>
    /// Cache 内部默认实现类
    /// </summary>
    internal class DefaultCache : ICache
    {
        private readonly ConcurrentDictionary<string, Lazy<AutoExpireCacheEntry<object>>> _cachePool = new ConcurrentDictionary<string, Lazy<AutoExpireCacheEntry<object>>>();
        private HttpClient _client;
        private CacheOptions _cacheOptions;
        public const string HttpClientName = "CacheList";
#if NETSTANDARD2_0 || NETCOREAPP3_0
        private readonly IHttpContextAccessor _accessor;
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public DefaultCache(IOptionsMonitor<CacheOptions> options, IHttpContextAccessor httpContextAccessor, IHttpClientFactory factory)
        {
            _client = factory.CreateClient(HttpClientName);
            _accessor = httpContextAccessor;
            options.OnChange(op =>
            {
                _cachePool.Clear();
                CorsClear(new List<string>() { "*" });
                _cacheOptions = op;
            });
            _cacheOptions = options.CurrentValue;
        }
#else
        /// <summary>
        /// 默认构造函数
        /// </summary>
        public DefaultCache()
        {
            _client = new HttpClient();
            _cacheOptions = CacheSection.GetOptions();
            _client.DefaultRequestHeaders.Connection.Add("keep-alive");
        }
#endif

        /// <summary>
        /// 通过指定 Key 获得配置缓存元素
        /// </summary>
        /// <param name="key">缓存真正使用的 Key</param>
        /// <param name="profileKey">配置文件中的 Key</param>
        /// <returns>CacheOption 实例</returns>
        private CacheOption RetrieveCacheOptionByKey(string key, string profileKey)
        {
            CacheOption option = null;
            if (_cacheOptions.Enabled) option = _cacheOptions.CacheItems.FirstOrDefault(item => item.Key == (profileKey ?? key));
            if (option == null) option = new CacheOption { Key = profileKey ?? key, Enabled = false };
            option.CacheKey = key;
            return option;
        }

        /// <summary>
        /// 获取或者添加缓存方法
        /// </summary>
        /// <typeparam name="T">缓存类型</typeparam>
        /// <param name="key">缓存键值</param>
        /// <param name="valueFactory">生成值的回调方法返回值</param>
        /// <param name="profileKey">为空时，使用缓存Key</param>
        /// <returns></returns>
        public T GetOrAdd<T>(string key, Func<string, T> valueFactory, string profileKey = null) => InternalGetOrAddOrUpdate(key, valueFactory, op => _cachePool.GetOrAdd(key, k => new Lazy<AutoExpireCacheEntry<object>>(() => CreateCacheEntry(k, op, valueFactory(k)))), profileKey);

        /// <summary>
        /// 获取或者添加缓存方法
        /// </summary>
        /// <typeparam name="T">缓存类型</typeparam>
        /// <param name="option">ICacheOption 实例</param>
        /// <param name="valueFactory">创建缓存值回调方法</param>
        /// <returns></returns>
        public T GetOrAdd<T>(ICacheOption option, Func<string, T> valueFactory) => InternalGetOrAddOrUpdate(option, valueFactory, op => _cachePool.GetOrAdd(op.Key, k => new Lazy<AutoExpireCacheEntry<object>>(() => CreateCacheEntry(k, op, valueFactory(k)))));

        /// <summary>
        /// 添加或者更新缓存方法
        /// </summary>
        /// <typeparam name="T">缓存类型</typeparam>
        /// <param name="key">缓存键值</param>
        /// <param name="valueFactory">创建缓存值回调方法</param>
        /// <param name="updateFactory">更新缓存值回调方法</param>
        /// <param name="profileKey">为空时，使用缓存Key</param>
        /// <returns></returns>
        public T AddOrUpdate<T>(string key, Func<string, T> valueFactory, Func<string, T, T> updateFactory, string profileKey = null) => InternalGetOrAddOrUpdate(key, valueFactory, op => _cachePool.AddOrUpdate(key, k => new Lazy<AutoExpireCacheEntry<object>>(() => CreateCacheEntry(k, op, valueFactory(k))), (k, c) => new Lazy<AutoExpireCacheEntry<object>>(() => CreateCacheEntry(k, op, updateFactory(k, (T)c.Value.Value)))), profileKey);

        /// <summary>
        /// 添加或者更新缓存方法
        /// </summary>
        /// <typeparam name="T">缓存类型</typeparam>
        /// <param name="option">ICacheOption 实例</param>
        /// <param name="valueFactory">创建缓存值回调方法</param>
        /// <param name="updateFactory">更新缓存值回调方法</param>
        /// <returns></returns>
        public T AddOrUpdate<T>(ICacheOption option, Func<string, T> valueFactory, Func<string, T, T> updateFactory) => InternalGetOrAddOrUpdate(option, valueFactory, op => _cachePool.AddOrUpdate(op.Key, k => new Lazy<AutoExpireCacheEntry<object>>(() => CreateCacheEntry(k, op, valueFactory(k))), (k, c) => new Lazy<AutoExpireCacheEntry<object>>(() => CreateCacheEntry(k, op, updateFactory(k, (T)c.Value.Value)))));

        private T InternalGetOrAddOrUpdate<T>(string key, Func<string, T> valueFactory, Func<ICacheOption, Lazy<AutoExpireCacheEntry<object>>> callback, string profileKey = null)
        {
            var option = RetrieveCacheOptionByKey(key, profileKey);
            if (!option.Enabled) option.Interval = 0;
            return InternalGetOrAddOrUpdate(option, valueFactory, callback);
        }

        private T InternalGetOrAddOrUpdate<T>(ICacheOption option, Func<string, T> valueFactory, Func<ICacheOption, Lazy<AutoExpireCacheEntry<object>>> callback)
        {
            // -1 表示永久缓存
            if (option.Interval < 1 && option.Interval != -1) return valueFactory(option.Key);
            var entry = callback(option);
            if (entry.Value?.Value == null) { ClearByKey(option.CacheKey); return default; }
            if (option.SlidingExpiration) entry.Value.Reset();
            return (T)entry.Value.Value;
        }

        /// <summary>
        /// 通过指定 Key 获取缓存方法
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">指定 Key</param>
        /// <returns>缓存内的值</returns>
        public T Get<T>(string key)
        {
            return _cachePool.TryGetValue(key, out var item) ? (T)item.Value.Value : default;
        }

        /// <summary>
        /// 缓存总数
        /// </summary>
        /// <returns></returns>
        public int Count => _cachePool.Count;

        private AutoExpireCacheEntry<object> CreateCacheEntry(string key, ICacheOption option, object value)
        {
            return new AutoExpireCacheEntry<object>(value, option.Interval, _ => ClearByKey(key)) { Desc = option.Desc };
        }

        /// <summary>
        /// 清除指定 key 集合方法
        /// </summary>
        /// <param name="keys"></param>
        public void Clear(IEnumerable<string> keys)
        {
            keys.ToList().ForEach(Clear);
        }

        /// <summary>
        /// 清除指定 key 方法
        /// </summary>
        /// <param name="key">结尾支持通配符*</param>
        public void Clear(string key = null)
        {
            if (string.IsNullOrEmpty(key))
            {
                _cachePool.Clear();
                return;
            }
            if (key.EndsWith("*", StringComparison.OrdinalIgnoreCase))
                _cachePool.Keys.Where(k => k.StartsWith(key.TrimEnd('*'), StringComparison.OrdinalIgnoreCase)).ToList().ForEach(ClearByKey);
            else
                ClearByKey(key);
        }

        private void ClearByKey(string key)
        {
            if (_cachePool.TryRemove(key, out var d))
            {
                d.Value.Dispose();
            }
        }

        /// <summary>
        /// 跨域清除指定 key 集合缓存方法
        /// </summary>
        /// <param name="keys"></param>
        public void CorsClear(IEnumerable<string> keys)
        {
            if (!_cacheOptions.Enabled) return;

            // 得到配置文件 CacheManagerCors 小节数据
            // 循环多站点
            _cacheOptions.CorsItems.Where(item => item.Enabled && !item.Self).AsParallel().ForAll(async ele =>
            {
                // 多个 Key 请求一次
                try
                {
                    var url = string.Empty;
                    var request = new HttpRequestMessage();
#if NETSTANDARD2_0 || NETCOREAPP3_0
                    var httpReq = _accessor.HttpContext?.Request;
                    if (httpReq == null) return;
                    var baseUrl = $"{httpReq.Scheme}://{httpReq.Host}{httpReq.PathBase}";
#else
                    var httpReq = HttpContext.Current.Request;
                    if (httpReq == null) return;
                    var baseUrl = $"{httpReq.Url.Scheme}://{httpReq.Url.Authority}";
#endif
                    url = ele.Url.Any(c => c == ':') ? ele.Url : $"{baseUrl}/{ele.Url}";
                    var cookie = httpReq.Headers["Cookie"];
                    request.Headers.Add("Cookie", cookie.ToString());
                    request.RequestUri = new Uri($"{url}?cacheKey={string.Join("|", keys)}");
                    await _client.SendAsync(request);
                }
                catch { }
            });
        }

        /// <summary>
        /// ToList 类型转化
        /// </summary>
        /// <returns></returns>
        public IEnumerable<ICacheItem> ToList()
        {
            return _cachePool.Select(item => new CacheItem
            {
                Key = item.Key,
                Interval = item.Value.Value.Period,
                Desc = item.Value.Value.Desc,
                Value = item.Value.Value.Value.GetType().Name,
                ElapsedSeconds = (int)DateTimeOffset.Now.Subtract(item.Value.Value.CreateTime).TotalSeconds
            });
        }

        /// <summary>
        /// 获取 缓存所有键值集合
        /// </summary>
        public IEnumerable<string> Keys
        {
            get { return _cachePool.Keys; }
        }

        /// <summary>
        /// 获取 所有跨域缓存配置项集合
        /// </summary>
        public IEnumerable<ICacheCorsItem> CorsSites { get { return _cacheOptions.Enabled ? _cacheOptions.CorsItems.Where(item => item.Enabled) : Enumerable.Empty<ICacheCorsItem>(); } }
    }
}
